﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using Newtonsoft.Json;

namespace TPLinkNetworking
{
    public class TPLinkConnection
    {
        private readonly string ChangeDeviceName = "{\"system\":{\"set_dev_alias\":{\"alias\":\"supercool plug\"}}}\r\n";
        private readonly string FactoryReset = "{\"system\":{\"reset\":{\"delay\":1}}}\r\n";
        private readonly string GetRealTimeEnergy = "{\"emeter\":{\"get_realtime\":{}}}\r\n";

        private readonly string GetSystemInformation = "{\"system\":{\"get_sysinfo\":null}}";
        private readonly string RebootSystem = "{\"system\":{\"reboot\":{\"delay\":1}}}";
        private readonly string TurnOff = "{\"system\":{\"set_relay_state\":{\"state\":0}}}\r\n";
        private readonly string TurnOffLedLight = "{\"system\":{\"set_led_off\":{\"off\":1}}}\r\n";
        private readonly string TurnOn = "{\"system\":{\"set_relay_state\":{\"state\":1}}}\r\n";
        private readonly string TurnOnLedLight = "{\"system\":{\"set_led_off\":{\"off\":0}}}\r\n";

        private static string SendCommand(SmartLink moduleLink, string command)
        {
            var encryptedData = Encrypt(command);
            var clientConnection = new TcpClient();
            var readbuf = new byte[2048];

            try
            {
                clientConnection.Connect(moduleLink.LinkAddress, moduleLink.LinkPort);
                var connectionStream = clientConnection.GetStream();
                var dataToSend = encryptedData;
                connectionStream.Write(dataToSend, 0, dataToSend.Length);
                connectionStream.ReadTimeout = 100000; // 10 second timeout on the read
                connectionStream.Read(readbuf, 0, 2048);
                connectionStream.Close();
            }
            catch
            {
                clientConnection.Close();
            }
            return Decrypt(readbuf);
        }

        /// <summary>
        ///     Gets specific information about the module
        /// </summary>
        /// <param name="moduleLink">the link object to use for connection</param>
        /// <returns>null if no connection or data</returns>
        public ModuleInformation GetModuleInformation(SmartLink moduleLink)
        {
            var moduleData = SendCommand(moduleLink, GetSystemInformation);
            return !string.IsNullOrWhiteSpace(moduleData)
                ? JsonConvert.DeserializeObject<ModuleInformation>(moduleData)
                : null;
        }

        /// <summary>
        /// Turn Relay on or off
        /// </summary>
        /// <param name="moduleLink">relay to operate</param>
        /// <param name="state">True on</param>
        /// <returns>Module information confirming the action</returns>
        public ModuleInformation SetRelayState(SmartLink moduleLink, bool state)
        {
            SendCommand(moduleLink,
                state
                    ? "{\"system\":{\"set_relay_state\":{\"state\":1}}}"
                    : "{\"system\":{\"set_relay_state\":{\"state\":0}}}");
            return GetModuleInformation(moduleLink);
        }

        /// <summary>
        /// ask for the current power consumption
        /// </summary>
        /// <param name="moduleLink"></param>
        /// <returns></returns>
        public ModuleInformation GetRealTimeEnergyUsage(SmartLink moduleLink)
        {
            var returnString = SendCommand(moduleLink, GetRealTimeEnergy);
            var model = !string.IsNullOrWhiteSpace(returnString)
                        ? JsonConvert.DeserializeObject<EnergyMonitor>(returnString)
                        : null;
            if (model == null) return moduleLink.ModuleInformation;
            model.emeter.timeStamp = DateTime.Now;
            moduleLink.ModuleInformation.EnergyStats.Add(model);
            return moduleLink.ModuleInformation;

        }

        public void UpdateModuleNameInformation(SmartLink moduleLink, string ModuleName)
        {
            SendCommand(moduleLink, "{\"system\":{\"set_dev_alias\":{\"alias\":\"" + ModuleName + "\"}}}");
            return;
        }

        public void UpdateModuleLedState(SmartLink moduleLink, bool state)
        {
            SendCommand(moduleLink, "{\"system\":{\"set_led_off\":{\"off\":" + (state ? "0": "1") + "}}}");

        }
        private static byte[] Encrypt(string message)
        {
            var key = 171; // Hard code for TPLink
            var result = new List<byte> { 0, 0, 0, 0 };
            foreach (var b in message)
            {
                var a = key ^ b; // this needs the unicode of the letter
                key = a;

                result.Add((byte)a);
            }
            return result.ToArray();
        }

        private static string Decrypt(IEnumerable<byte> message)
        {
            var key = 171; // Hard code for TPLink
            var result = new List<byte>();
            foreach (var b in message)
            {
                var a = key ^ b;
                key = b;
                result.Add((byte)a);
            }
            for (var i = 0; i < 5; i++)
                result[i] = 0;
            result[4] = 123; // Fix the Json
            var test = Encoding.Default.GetString(result.ToArray()).Replace("\0", "");
            test = test.Substring(0, test.LastIndexOf("}", StringComparison.Ordinal) + 1);
            return test;
        }

        public class SmartLink
        {
            public SmartLink()
            {
                LinkPort = 9999;
            }

            public string LinkName { get; set; }
            public string LinkAddress { get; set; }
            public int LinkPort { get; set; }
            public ModuleInformation ModuleInformation { get; set; }
        }
    }
}